#pragma once
#include <cmath>
#include <string>
#include "vector.h"
#include <Common/device_type.h>


namespace shermit {

	template<typename Float_t>
	class AABB2 {
		using vec_t = nv::vec2<Float_t>;
	public:
		using value_type = Float_t;
		GENERAL AABB2() { reset(); }
		GENERAL AABB2(const AABB2<Float_t>& b) { ptMin = b.ptMin; ptMax = b.ptMax; }
		GENERAL AABB2(const AABB2<Float_t>& a, const AABB2<Float_t>& b) { 
			ptMin = nv::min(a.ptMin, b.ptMin);
			ptMax = nv::max(a.ptMax, b.ptMax);
		}
		GENERAL AABB2(const vec_t& ptMin, const vec_t& ptMax) : ptMin(ptMin), ptMax(ptMax) {}
		GENERAL AABB2(Float_t minx, Float_t miny,
			Float_t maxx, Float_t maxy)
			: ptMin(minx, miny),
			ptMax(maxx, maxy) {}

		GENERAL vec_t center() { return (ptMax + ptMin) * 0.5; }

		GENERAL void merge(const AABB2<Float_t>& b);

		GENERAL void merge(const vec_t& b);

		GENERAL void merge(const Float_t x, const Float_t y);

		GENERAL bool overlap(const AABB2<Float_t>& b) const;

		GENERAL bool contain(const AABB2<Float_t>& b) const;

		GENERAL void reset();

		__host__ std::string toString();

		vec_t ptMin, ptMax;
	};


	// structure of array, widely used in GPU
	template<typename Float_t>
	class AABB2_SOA {
		using vec_t = nv::vec2<Float_t>;
	public:
		using bv_struct_t = AABB2<Float_t> ;  // BV type in the form of structure
		Float_t* minx, * miny;
		Float_t* maxx, * maxy;

		void allocateHost(size_t size);
		void deallocateHost();
		void resetHost(size_t size);
#if enable_cuda
		void allocateDevice(size_t size);
		void deallocateDevice();
		void resetDevice(size_t size);
#endif

		GENERAL AABB2_SOA() : minx(nullptr), miny(nullptr), maxx(nullptr), maxy(nullptr) {}
		// shallow copy
		GENERAL AABB2_SOA(const AABB2_SOA<Float_t>& b) {
			minx = b.minx;
			miny = b.miny;
			maxx = b.maxx;
			maxy = b.maxy;
		}

		GENERAL vec_t center(size_t i) const {
			return vec_t(
				(minx[i] + maxx[i]) * 0.5f,
				(miny[i] + maxy[i]) * 0.5f
			);
		}

		GENERAL Float_t area(size_t i) const {
			return (maxx[i] - minx[i]) * (maxy[i] - miny[i]);
		}

		GENERAL void setBV(size_t i, const AABB2_SOA<Float_t>& bvs, size_t j) {
			minx[i] = bvs.minx[j], miny[i] = bvs.miny[j];
			maxx[i] = bvs.maxx[j], maxy[i] = bvs.maxy[j];
		}

		GENERAL void setBV(size_t i, const AABB2<Float_t>& bv) {
			minx[i] = bv.ptMin.x, miny[i] = bv.ptMin.y;
			maxx[i] = bv.ptMax.x, maxy[i] = bv.ptMax.y;
		}

		GENERAL void setBV(
			size_t i, 
			const AABB2_SOA<Float_t>& a, size_t ia,
			const AABB2_SOA<Float_t>& b, size_t ib) {
			minx[i] = fmin(a.minx[ia], b.minx[ib]);
			miny[i] = fmin(a.miny[ia], b.miny[ib]);
			maxx[i] = fmax(a.maxx[ia], b.maxx[ib]);
			maxy[i] = fmax(a.maxy[ia], b.maxy[ib]);
		}

		GENERAL AABB2<Float_t> getBV(size_t i) const {
			return AABB2<Float_t>(minx[i], miny[i], maxx[i], maxy[i]);
		}

		GENERAL void merge(size_t idx, const Float_t x, const Float_t y) {
			minx[idx] = fmin(minx[idx], x);
			miny[idx] = fmin(miny[idx], y);
			maxx[idx] = fmax(maxx[idx], x);
			maxy[idx] = fmax(maxy[idx], y);
		}

		GENERAL void merge(size_t idx, AABB2<Float_t>& bv) {
			minx[idx] = fmin(minx[idx], bv.ptMin.x);
			miny[idx] = fmin(miny[idx], bv.ptMin.y);
			maxx[idx] = fmax(maxx[idx], bv.ptMax.x);
			maxy[idx] = fmax(maxy[idx], bv.ptMax.y);
		}

		GENERAL bool overlap(size_t i, const AABB2<Float_t>& b) const {
			if (b.ptMin.x > maxx[i] || b.ptMax.x < minx[i]) return false;
			if (b.ptMin.y > maxy[i] || b.ptMax.y < miny[i]) return false;
			return true;
		}

		GENERAL bool overlap(size_t i, size_t j) const {
			if (minx[j] > maxx[i] || maxx[j] < minx[i]) return false;
			if (miny[j] > maxy[i] || maxy[j] < miny[i]) return false;
			return true;
		}
		GENERAL bool contain(size_t i, const vec_t& v) const {
			return v.x <= maxx[i] && v.x >= minx[i] &&
				v.y <= maxy[i] && v.y >= miny[i];
		}
	};

	template<typename Float_t>
	class AABB3 {
		using vec_t = nv::vec3<Float_t>;
	public:
		using value_type = Float_t;
		GENERAL AABB3() { reset(); }
		GENERAL AABB3(const AABB3<Float_t>& b) { ptMin = b.ptMin; ptMax = b.ptMax; }
		GENERAL AABB3(const AABB3<Float_t>& a, const AABB3<Float_t>& b) {
			ptMin = nv::min(a.ptMin, b.ptMin);
			ptMax = nv::max(a.ptMax, b.ptMax);
		}
		GENERAL AABB3(const vec_t& ptMin, const vec_t& ptMax) : ptMin(ptMin), ptMax(ptMax) {}
		GENERAL AABB3(Float_t minx, Float_t miny, Float_t minz,
			Float_t maxx, Float_t maxy, Float_t maxz)
			: ptMin(minx, miny, minz),
			ptMax(maxx, maxy, maxz) {}

		GENERAL vec_t center() { return (ptMax + ptMin) * 0.5; }

		GENERAL void merge(const AABB3<Float_t>& b);

		GENERAL void merge(const vec_t& b);

		GENERAL void merge(const Float_t x, const Float_t y, const Float_t z);

		GENERAL bool overlap(const AABB3<Float_t>& b) const;

		GENERAL bool contain(const AABB3<Float_t>& b) const;

		GENERAL void reset();

		__host__ std::string toString();

		vec_t ptMin, ptMax;
	};


	// structure of array, widely used in GPU
	template<typename Float_t>
	class AABB3_SOA {
		using vec_t = nv::vec3<Float_t>;
	public:
		using bv_struct_t = AABB3<Float_t>;  // BV type in the form of structure
		Float_t* minx, * miny, *minz;
		Float_t* maxx, * maxy, *maxz;

		void allocateHost(size_t size);
		void deallocateHost();
		void resetHost(size_t size);
#if enable_cuda
		void allocateDevice(size_t size);
		void deallocateDevice();
		void resetDevice(size_t size);
#endif

		GENERAL AABB3_SOA() : minx(nullptr), miny(nullptr), minz(nullptr), maxx(nullptr), maxy(nullptr), maxz(nullptr) {}
		// shallow copy
		GENERAL AABB3_SOA(const AABB3_SOA<Float_t>& b) {
			minx = b.minx;
			miny = b.miny;
			minz = b.minz;
			maxx = b.maxx;
			maxy = b.maxy;
			maxz = b.maxz;
		}

		GENERAL vec_t center(size_t i) const {
			return vec_t(
				(minx[i] + maxx[i]) * 0.5f,
				(miny[i] + maxy[i]) * 0.5f,
				(minz[i] + maxz[i]) * 0.5f
			);
		}

		GENERAL Float_t volume(size_t i) const {
			return (maxx[i] - minx[i]) * (maxy[i] - miny[i]) * (maxz[i] - minz[i]);
		}

		GENERAL void setBV(size_t i, const AABB3_SOA<Float_t>& bvs, size_t j) {
			minx[i] = bvs.minx[j], miny[i] = bvs.miny[j], minz[i] = bvs.minz[j];
			maxx[i] = bvs.maxx[j], maxy[i] = bvs.maxy[j], maxz[i] = bvs.maxz[j];
		}

		GENERAL void setBV(size_t i, const AABB3<Float_t>& bv) {
			minx[i] = bv.ptMin.x, miny[i] = bv.ptMin.y, minz[i] = bv.ptMin.z;
			maxx[i] = bv.ptMax.x, maxy[i] = bv.ptMax.y, maxz[i] = bv.ptMax.z;
		}

		GENERAL void setBV(
			size_t i,
			const AABB3_SOA<Float_t>& a, size_t ia,
			const AABB3_SOA<Float_t>& b, size_t ib) {
			minx[i] = fmin(a.minx[ia], b.minx[ib]);
			miny[i] = fmin(a.miny[ia], b.miny[ib]);
			minz[i] = fmin(a.minz[ia], b.minz[ib]);
			maxx[i] = fmax(a.maxx[ia], b.maxx[ib]);
			maxy[i] = fmax(a.maxy[ia], b.maxy[ib]);
			maxz[i] = fmax(a.maxz[ia], b.maxz[ib]);
		}

		GENERAL AABB3<Float_t> getBV(size_t i) const {
			return AABB3<Float_t>(minx[i], miny[i], minz[i], maxx[i], maxy[i], maxz[i]);
		}

		GENERAL void merge(size_t idx, const Float_t x, const Float_t y, const Float_t z) {
			minx[idx] = fmin(minx[idx], x);
			miny[idx] = fmin(miny[idx], y);
			minz[idx] = fmin(minz[idx], z);
			maxx[idx] = fmax(maxx[idx], x);
			maxy[idx] = fmax(maxy[idx], y);
			maxz[idx] = fmax(maxz[idx], z);
		}

		GENERAL void merge(size_t idx, AABB3<Float_t>& bv) {
			minx[idx] = fmin(minx[idx], bv.ptMin.x);
			miny[idx] = fmin(miny[idx], bv.ptMin.y);
			minz[idx] = fmin(minz[idx], bv.ptMin.z);
			maxx[idx] = fmax(maxx[idx], bv.ptMax.x);
			maxy[idx] = fmax(maxy[idx], bv.ptMax.y);
			maxz[idx] = fmax(maxz[idx], bv.ptMax.z);
		}

		GENERAL bool overlap(size_t i, const AABB3<Float_t>& b) const {
			if (b.ptMin.x > maxx[i] || b.ptMax.x < minx[i]) return false;
			if (b.ptMin.y > maxy[i] || b.ptMax.y < miny[i]) return false;
			if (b.ptMin.z > maxz[i] || b.ptMax.z < minz[i]) return false;
			return true;
		}

		GENERAL bool overlap(size_t i, size_t j) const {
			if (minx[j] > maxx[i] || maxx[j] < minx[i]) return false;
			if (miny[j] > maxy[i] || maxy[j] < miny[i]) return false;
			if (minz[j] > maxz[i] || maxz[j] < minz[i]) return false;
			return true;
		}
		GENERAL bool contain(size_t i, const vec_t& v) const {
			return v.x <= maxx[i] && v.x >= minx[i] &&
				v.y <= maxy[i] && v.y >= miny[i] &&
				v.z <= maxz[i] && v.z >= minz[i];
		}
	};

}